
#include "CollisionChecker.h"
#include "CollisionModel.h"
#include "../SceneObjectSet.h"
#include "../SceneObject.h"
#include "../Robot.h"
#include "../VirtualRobotException.h"

#include <cfloat>

#include <Eigen/Core>
#include <Eigen/Geometry>


#if defined(VR_COLLISION_DETECTION_PQP)
#define COL_CHECKER_IMPL CollisionCheckerPQP
#else
#define COL_CHECKER_IMPL CollisionCheckerDummy
#endif




namespace VirtualRobot
{
    using std::endl;

    namespace
    {
        std::mutex mutex;
    }

    CollisionCheckerPtr CollisionChecker::globalCollisionChecker;

    CollisionChecker::Cleanup::~Cleanup()
    {
        std::scoped_lock lock(mutex);
        CollisionChecker::globalCollisionChecker.reset();
    }


    CollisionCheckerPtr CollisionChecker::getGlobalCollisionChecker()
    {
        static Cleanup _Cleanup;

        if (true)
        {
            std::scoped_lock lock(mutex);

            if (!globalCollisionChecker)
            {
                globalCollisionChecker.reset(new CollisionChecker());
            }
        }

        return globalCollisionChecker;
    }

    //----------------------------------------------------------------------
    // class CollisionChecker constructor
    //----------------------------------------------------------------------
    CollisionChecker::CollisionChecker()
    {
        initialized = true;
        debugOutput = false; // ENABLES OUTPUT OF COLLISION MODEL TRIANGLES
        automaticSizeCheck = true;

        collisionCheckerImplementation.reset(new COL_CHECKER_IMPL());
    }

    //----------------------------------------------------------------------
    // class CollisionChecker destructor
    //----------------------------------------------------------------------
    CollisionChecker::~CollisionChecker()
    = default;


    float CollisionChecker::calculateDistance(const SceneObjectSetPtr& model1, const SceneObjectSetPtr& model2)
    {
        VR_ASSERT(model1 && model2);
        return calculateDistance(model1, model2, tmpV1, tmpV2, nullptr, nullptr);
    }

    float CollisionChecker::calculateDistance(const CollisionModelPtr& model1, const SceneObjectSetPtr& model2)
    {
        VR_ASSERT(model1 && model2);
        return calculateDistance(model1, model2, tmpV1, tmpV2, nullptr, nullptr);
    }

    float CollisionChecker::calculateDistance(const CollisionModelPtr& model1, const CollisionModelPtr& model2)
    {
        VR_ASSERT(model1 && model2);
        return calculateDistance(model1, model2, tmpV1, tmpV2, nullptr, nullptr);
    }


    float CollisionChecker::calculateDistance(const SceneObjectPtr& model1, const SceneObjectSetPtr& model2)
    {
        VR_ASSERT(model1 && model2);
        RobotPtr r = std::dynamic_pointer_cast<Robot>(model1);

        if (r)
        {
            SceneObjectSetPtr robotModels = getRobotModels(r);
            return calculateDistance(robotModels, model2);
        }
        else
        {
            return calculateDistance(model1->getCollisionModel(), model2, tmpV1, tmpV2, nullptr, nullptr);
        }
    }

    float CollisionChecker::calculateDistance(const SceneObjectPtr& model1, const SceneObjectPtr& model2)
    {
        VR_ASSERT(model1 && model2);
        RobotPtr r = std::dynamic_pointer_cast<Robot>(model1);
        RobotPtr r2 = std::dynamic_pointer_cast<Robot>(model2);

        if (r && r2)
        {
            SceneObjectSetPtr robotModels = getRobotModels(r);
            SceneObjectSetPtr robotModels2 = getRobotModels(r2);
            return calculateDistance(robotModels, robotModels2);
        }
        else if (r && !r2)
        {
            SceneObjectSetPtr robotModels = getRobotModels(r);
            return calculateDistance(model2, robotModels);
        }
        else if (!r && r2)
        {
            SceneObjectSetPtr robotModels2 = getRobotModels(r2);
            return calculateDistance(model1, robotModels2);
        }
        else
        {
            return calculateDistance(model1->getCollisionModel(), model2->getCollisionModel(), tmpV1, tmpV2, nullptr, nullptr);
        }
    }


    float CollisionChecker::calculateDistance(const SceneObjectPtr& model1, const SceneObjectSetPtr& model2, Eigen::Vector3f& P1, Eigen::Vector3f& P2, int* trID1, int* trID2)
    {
        VR_ASSERT(model1 && model2);
        RobotPtr r = std::dynamic_pointer_cast<Robot>(model1);

        if (r)
        {
            SceneObjectSetPtr robotModels = getRobotModels(r);
            return calculateDistance(robotModels, model2, P1, P2, trID1, trID2);
        }
        else
        {
            return calculateDistance(model1->getCollisionModel(), model2, P1, P2, trID1, trID2);
        }
    }

    float CollisionChecker::calculateDistance(const SceneObjectPtr& model1, const SceneObjectPtr& model2, Eigen::Vector3f& P1, Eigen::Vector3f& P2, int* trID1, int* trID2)
    {
        VR_ASSERT(model1 && model2);
        RobotPtr r = std::dynamic_pointer_cast<Robot>(model1);
        RobotPtr r2 = std::dynamic_pointer_cast<Robot>(model2);

        if (r && r2)
        {
            SceneObjectSetPtr robotModels = getRobotModels(r);
            SceneObjectSetPtr robotModels2 = getRobotModels(r2);
            return calculateDistance(robotModels, robotModels2, P1, P2, trID1, trID2);
        }
        else if (r && !r2)
        {
            SceneObjectSetPtr robotModels = getRobotModels(r);
            return calculateDistance(model2, robotModels, P2, P1, trID2, trID1);
        }
        else if (!r && r2)
        {
            SceneObjectSetPtr robotModels2 = getRobotModels(r2);
            return calculateDistance(model1, robotModels2, P1, P2, trID1, trID2);
        }
        else
        {
            return calculateDistance(model1->getCollisionModel(), model2->getCollisionModel(), P1, P2, trID1, trID2);
        }
    }

    float CollisionChecker::calculateDistance(const SceneObjectSetPtr& model1, const SceneObjectSetPtr& model2, Eigen::Vector3f& P1, Eigen::Vector3f& P2, int* trID1, int* trID2)
    {
        VR_ASSERT(model1 && model2);
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == model2->getCollisionChecker(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == shared_from_this(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT(isInitialized());

        std::vector< CollisionModelPtr > colModels1 = model1->getCollisionModels();
        std::vector< CollisionModelPtr > colModels2 = model2->getCollisionModels();

        if (colModels1.size() == 0 || colModels2.size() == 0)
        {
            VR_WARNING << "no internal data..." << std::endl;
            return -1.0f;
        }

        float fResult = FLT_MAX;
        Eigen::Vector3f v1;
        Eigen::Vector3f v2;
        int trID1_r;
        int trID2_r;
        std::vector< CollisionModelPtr >::iterator it1 = colModels1.begin();

        while (it1 != colModels1.end())
        {
            std::vector< CollisionModelPtr >::iterator it2 = colModels2.begin();

            while (it2 != colModels2.end())
            {
                float fRes = calculateDistance(*it1, *it2, v1, v2, &trID1_r, &trID2_r);

                if (fRes <= fResult)
                {
                    fResult = fRes;
                    P1 = v1;
                    P2 = v2;

                    if (trID1)
                    {
                        *trID1 = trID1_r;
                    }

                    if (trID2)
                    {
                        *trID2 = trID2_r;
                    }
                }

                it2++;
            }

            it1++;
        }

        return fResult;
    }

    float CollisionChecker::calculateDistance(const CollisionModelPtr& model1, const SceneObjectSetPtr& model2, Eigen::Vector3f& P1, Eigen::Vector3f& P2, int* trID1, int* trID2)
    {
        VR_ASSERT(model1 && model2);
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == model2->getCollisionChecker(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == shared_from_this(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT(isInitialized());

        std::vector< CollisionModelPtr > colModels = model2->getCollisionModels();

        if (colModels.size() == 0)
        {
            VR_WARNING << "no internal data..." << std::endl;
            return -1.0f;
        }

        float fResult = FLT_MAX;
        Eigen::Vector3f v1;
        Eigen::Vector3f v2;
        int trID1_r;
        int trID2_r;
        std::vector< CollisionModelPtr >::iterator it = colModels.begin();

        while (it != colModels.end())
        {
            float fRes = calculateDistance(model1, *it, v1, v2, &trID1_r, &trID2_r);

            if (fRes <= fResult)
            {
                fResult = fRes;
                P1 = v1;
                P2 = v2;

                if (trID1)
                {
                    *trID1 = trID1_r;
                }

                if (trID2)
                {
                    *trID2 = trID2_r;
                }
            }

            it++;
        }

        return fResult;
    }

    float CollisionChecker::calculateDistance(const CollisionModelPtr& model1, const CollisionModelPtr& model2, Eigen::Vector3f& P1, Eigen::Vector3f& P2, int* trID1, int* trID2)
    {
        VR_ASSERT(model1 && model2);
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == model2->getCollisionChecker(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == shared_from_this(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT(isInitialized());

        return collisionCheckerImplementation->calculateDistance(model1, model2, P1, P2, trID1, trID2);
    }



    bool CollisionChecker::checkCollision(const std::vector<CollisionModelPtr>& model1, const CollisionModelPtr& model2)
    {
        VR_ASSERT(model2);
        VR_ASSERT(isInitialized());

        if (model1.size() == 0)
        {
            VR_WARNING << "no internal data..." << std::endl;
            return false;
        }

        for (const auto& m1 : model1)
        {
            if (checkCollision(m1, model2))
            {
                return true;
            }
        }

        return false;
    }

    bool CollisionChecker::checkCollision(const SceneObjectSetPtr& model1, const SceneObjectSetPtr& model2)
    {
        VR_ASSERT(model1 && model2);
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == model2->getCollisionChecker(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == shared_from_this(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT(isInitialized());

        std::vector< CollisionModelPtr > vColModels1 = model1->getCollisionModels();
        std::vector< CollisionModelPtr > vColModels2 = model2->getCollisionModels();

        if (vColModels1.size() == 0)
        {
            VR_WARNING << "no internal data for " << model1->getName() << std::endl;
            return false;
        }
        if ( vColModels2.size() == 0)
        {
            VR_WARNING << "no internal data for " << model2->getName() << std::endl;
            return false;
        }

        std::vector< CollisionModelPtr >::iterator it1 = vColModels1.begin();

        while (it1 != vColModels1.end())
        {
            std::vector< CollisionModelPtr >::iterator it2 = vColModels2.begin();

            while (it2 != vColModels2.end())
            {
                if (checkCollision(*it1, *it2))
                {
                    return true;
                }

                it2++;
            }

            it1++;
        }

        return false;
    }

    bool CollisionChecker::checkCollision(const SceneObjectPtr& model1, const SceneObjectSetPtr& model2)
    {
        VR_ASSERT(model1 && model2);
        RobotPtr r = std::dynamic_pointer_cast<Robot>(model1);

        if (r)
        {
            SceneObjectSetPtr robotModels = getRobotModels(r);
            return checkCollision(robotModels, model2);
        }
        else
        {
            return checkCollision(model1->getCollisionModel(), model2);
        }
    }

    SceneObjectSetPtr CollisionChecker::getRobotModels(const RobotPtr& r)
    {
        SceneObjectSetPtr result(new SceneObjectSet(r->getName(), shared_from_this()));
        std::vector<RobotNodePtr> cm = r->getRobotNodes();

        for (auto & i : cm)
        {
            if (i->getCollisionModel())
            {
                result->addSceneObject(i);
            }
        }

        return result;
    }


    bool CollisionChecker::checkCollision(const CollisionModelPtr& model1, const SceneObjectSetPtr& model2)
    {
        VR_ASSERT(model1 && model2);
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == model2->getCollisionChecker(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == shared_from_this(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT(isInitialized());

        std::vector< CollisionModelPtr > vColModels = model2->getCollisionModels();

        if (vColModels.size() == 0)
        {
            VR_WARNING << "no internal data..." << std::endl;
            return false;
        }

        std::vector< CollisionModelPtr >::iterator it = vColModels.begin();

        while (it != vColModels.end())
        {
            if (checkCollision(model1, *it))
            {
                return true;
            }

            it++;
        }

        return false;
    }

    bool CollisionChecker::checkCollision(const SceneObjectPtr& model1, const SceneObjectPtr& model2)
    {
        VR_ASSERT(model1 && model2);
        RobotPtr r = std::dynamic_pointer_cast<Robot>(model1);
        RobotPtr r2 = std::dynamic_pointer_cast<Robot>(model2);

        if (r && r2)
        {
            SceneObjectSetPtr robotModels = getRobotModels(r);
            SceneObjectSetPtr robotModels2 = getRobotModels(r2);
            return checkCollision(robotModels, robotModels2);
        }
        else if (r && !r2)
        {
            SceneObjectSetPtr robotModels = getRobotModels(r);
            return checkCollision(model2, robotModels);
        }
        else if (!r && r2)
        {
            SceneObjectSetPtr robotModels2 = getRobotModels(r2);
            return checkCollision(model1, robotModels2);
        }
        else
        {
            return checkCollision(model1->getCollisionModel(), model2->getCollisionModel());
        }
    }

    bool CollisionChecker::checkCollision(const CollisionModelPtr& model, const Eigen::Vector3f &point, float tolerance)
    {
        VR_ASSERT(model);
        VR_ASSERT_MESSAGE(model->getCollisionChecker() == shared_from_this(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT(isInitialized());

        return collisionCheckerImplementation->checkCollision(model, point, tolerance);//, storeContact);
    }

    bool CollisionChecker::checkCollision(const SceneObjectSetPtr& modelSet, const Eigen::Vector3f &point, float tolerance)
    {
        VR_ASSERT(modelSet);
        VR_ASSERT_MESSAGE(modelSet->getCollisionChecker() == shared_from_this(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT(isInitialized());

        std::vector< CollisionModelPtr > vColModels1 = modelSet->getCollisionModels();

        if (vColModels1.size() == 0)
        {
            VR_WARNING << "no internal data for " << modelSet->getName() << std::endl;
            return false;
        }


        for(auto & col : vColModels1)
        {
            if(checkCollision(col, point, tolerance))
            {
                return true;
            }
        }
        return false;
    }

    bool CollisionChecker::checkCollision(const CollisionModelPtr& model1, const CollisionModelPtr& model2)
    {
        THROW_VR_EXCEPTION_IF(!model1, "model1 is null");
        THROW_VR_EXCEPTION_IF(!model2, "model2 is null");
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == model2->getCollisionChecker(), "Collision models are linked to different Collision Checker instances");
        VR_ASSERT_MESSAGE(model1->getCollisionChecker() == shared_from_this(), "Collision models are linked to different Collision Checker instances");
        THROW_VR_EXCEPTION_IF(!isInitialized(), "checker not initialized");

        return collisionCheckerImplementation->checkCollision(model1, model2);//, storeContact);
    }

    /*
    bool CollisionChecker::getAllCollisonTriangles (SceneObjectSetPtr model1, SceneObjectSetPtr model2, std::vector<int> &storePairs)
    {
        if (!model1 || !model2)
        {
            printf ("CollisionChecker:GetAllCollisongTriangles - NULL data...\n");
            return false;
        }
        if (model1->getCollisionChecker() != model2->getCollisionChecker() || model1->getCollisionChecker()!=shared_from_this())
        {
            VR_WARNING << "Could not go on, collision models are linked to different Collision Checker instances." << std::endl;
            return false;
        }
        if (!isInitialized())
        {
            VR_WARNING << "not initialized." << std::endl;
            return false;
        }
        return collisionCheckerImplementation->getAllCollisonTriangles(model1,model2,storePairs);
    }*/


    void CollisionChecker::setAutomaticSizeCheck(bool checkSizeOnColModelCreation)
    {
        automaticSizeCheck = checkSizeOnColModelCreation;
        collisionCheckerImplementation->setAutomaticSizeCheck(automaticSizeCheck);
    }

    /*
    bool CollisionChecker::checkCollision( SbXfBox3f& box1, SbXfBox3f& box2 )
    {
        ////return box1.intersect(box2);

        //box1.setBounds(-100, -100, -100, 100, 100, 100);
        //box2.setBounds(-100, -100, -100, 100, 100, 100);

        //SbMatrix tr;
        //tr.setTranslate(SbVec3f(0,0,0));
        //box1.setTransform(tr);

        //tr.setTranslate(SbVec3f(0, 0, 250));
        //box2.setTransform(tr);

        // OBB Intersection test from Neoengine

        const float fParallellCutoff = 0.99999f;
        bool bParallellAxes = false;

        SbRotation kRotA;
        SbRotation kRotB;
        SbVec3f kTransA;
        SbVec3f kTransB;
        SbMatrix kRotMatA;
        SbMatrix kRotMatB;
        SbVec3f tmpV;
        SbRotation tmpR;

        box1.getTransform().getTransform(kTransA, kRotA, tmpV, tmpR);
        box2.getTransform().getTransform(kTransB, kRotB, tmpV, tmpR);
        kRotMatA.setRotate(kRotA);
        kRotMatB.setRotate(kRotB);

        SbVec3f akAxesA[3] = { SbVec3f(kRotMatA[0]), SbVec3f(kRotMatA[1]), SbVec3f(kRotMatA[2]) };
        SbVec3f akAxesB[3] = { SbVec3f(kRotMatB[0]), SbVec3f(kRotMatB[1]), SbVec3f(kRotMatB[2]) };

        float afDimA[3];
        float afDimB[3];

        float dx, dy, dz;
        box1.getSize(dx, dy, dz);
        afDimA[0] = dx/2;
        afDimA[1] = dy/2;
        afDimA[2] = dz/2;

        box2.getSize(dx, dy, dz);
        afDimB[0] = dx/2;
        afDimB[1] = dy/2;
        afDimB[2] = dz/2;

        //Difference of box positions
        SbVec3f kDiff = kTransB - kTransA;

        ////////Early out test
        //float radiusA2 = 0, radiusB2 = 0;

        //SbBox3f aabb1(box1);
        //SbBox3f aabb2(box2);
        //radiusA2 = (aabb1.getMax()-aabb1.getCenter()).length();
        //radiusB2 = (aabb2.getMax()-aabb2.getCenter()).length();
        //if( ( radiusA2 + radiusB2 ) < kDiff.length() )
        //  return false;

        float afAxisDot[3][3];    // afAxisDot[i][j]    = akAxesA[i]Ptr  akAxesB[j];
        float afAbsAxisDot[3][3]; // afAbsAxisDot[i][j] = |afAxisDot[i][j]|

        float afAxesADotDiff[3];  // afAxesADotDiff[i]  = akAxesA[i]Ptr  kDiff

        //Test all 15 possible separating axes

        //Axis Ax
        //First calculate AxPtr  Bi axis dot products (length of each B axis along Ax)
        afAxisDot[0][0] = akAxesA[0].dot(akAxesB[0]);
        afAxisDot[0][1] = akAxesA[0].dot(akAxesB[1]);
        afAxisDot[0][2] = akAxesA[0].dot(akAxesB[2]);

        //Get absolute value of dot products
        afAbsAxisDot[0][0] = fabsf( afAxisDot[0][0] ); if( afAbsAxisDot[0][0] > fParallellCutoff ) bParallellAxes = true;
        afAbsAxisDot[0][1] = fabsf( afAxisDot[0][1] ); if( afAbsAxisDot[0][1] > fParallellCutoff ) bParallellAxes = true;
        afAbsAxisDot[0][2] = fabsf( afAxisDot[0][2] ); if( afAbsAxisDot[0][2] > fParallellCutoff ) bParallellAxes = true;

        //calculate AxPtr  D dot product (length of center difference along Ax axis)
        afAxesADotDiff[0]  = akAxesA[0].dot(kDiff);

        //  int     iDeepAxis = 0;
        //  SbVec3f kNormal;

        //  float fOverlapMax  = -numeric_limits< float >::max();
        float fOverlap;

        //Check if distance between centers along axis is greater than sum of boxes dimensions along axis
        if( ( fOverlap = fabsf( afAxesADotDiff[0] ) - ( ( afDimA[0] ) + ( afDimB[0]Ptr  afAbsAxisDot[0][0] + afDimB[1]Ptr  afAbsAxisDot[0][1] + afDimB[2]Ptr  afAbsAxisDot[0][2] ) ) ) > 0.0f )
            return false;

        //Axis Ay
        afAxisDot[1][0] = akAxesA[1].dot(akAxesB[0]);
        afAxisDot[1][1] = akAxesA[1].dot(akAxesB[1]);
        afAxisDot[1][2] = akAxesA[1].dot(akAxesB[2]);

        afAbsAxisDot[1][0] = fabsf( afAxisDot[1][0] ); if( afAbsAxisDot[1][0] > fParallellCutoff ) bParallellAxes = true;
        afAbsAxisDot[1][1] = fabsf( afAxisDot[1][1] ); if( afAbsAxisDot[1][1] > fParallellCutoff ) bParallellAxes = true;
        afAbsAxisDot[1][2] = fabsf( afAxisDot[1][2] ); if( afAbsAxisDot[1][2] > fParallellCutoff ) bParallellAxes = true;

        afAxesADotDiff[1]  = akAxesA[1].dot(kDiff);

        if( ( fOverlap = fabsf( afAxesADotDiff[1] ) - ( ( afDimA[1] ) + ( afDimB[0]Ptr  afAbsAxisDot[1][0] + afDimB[1]Ptr  afAbsAxisDot[1][1] + afDimB[2]Ptr  afAbsAxisDot[1][2] ) ) ) > 0.0f )
            return false;

        //Axis Az
        afAxisDot[2][0] = akAxesA[2].dot(akAxesB[0]);
        afAxisDot[2][1] = akAxesA[2].dot(akAxesB[1]);
        afAxisDot[2][2] = akAxesA[2].dot(akAxesB[2]);

        afAbsAxisDot[2][0] = fabsf( afAxisDot[2][0] ); if( afAbsAxisDot[2][0] > fParallellCutoff ) bParallellAxes = true;
        afAbsAxisDot[2][1] = fabsf( afAxisDot[2][1] ); if( afAbsAxisDot[2][1] > fParallellCutoff ) bParallellAxes = true;
        afAbsAxisDot[2][2] = fabsf( afAxisDot[2][2] ); if( afAbsAxisDot[2][2] > fParallellCutoff ) bParallellAxes = true;

        afAxesADotDiff[2]  = akAxesA[2].dot(kDiff);

        if( ( fOverlap = fabsf( afAxesADotDiff[2] ) - ( ( afDimA[2] ) + ( afDimB[0]Ptr  afAbsAxisDot[2][0] + afDimB[1]Ptr  afAbsAxisDot[2][1] + afDimB[2]Ptr  afAbsAxisDot[2][2] ) ) ) > 0.0f )
            return false;

        float fProj;
        //  float fScale;

        //Axis Bx
        //We already have all axis*axis dot products, only calculate center difference along Bx axis and compare
        if( ( fOverlap = fabsf( ( fProj = akAxesB[0].dot(kDiff) ) ) - ( ( afDimA[0]Ptr  afAbsAxisDot[0][0] + afDimA[1]Ptr  afAbsAxisDot[1][0] + afDimA[2]Ptr  afAbsAxisDot[2][0] ) + ( afDimB[0] ) ) ) > 0.0f )
            return false;

        //Axis By
        if( ( fOverlap = fabsf( ( fProj = akAxesB[1].dot(kDiff) ) ) - ( ( afDimA[0]Ptr  afAbsAxisDot[0][1] + afDimA[1]Ptr  afAbsAxisDot[1][1] + afDimA[2]Ptr  afAbsAxisDot[2][1] ) + ( afDimB[1] ) ) ) > 0.0f )
            return false;

        //Axis Bz
        if( ( fOverlap = fabsf( ( fProj = akAxesB[2].dot(kDiff) ) ) - ( ( afDimA[0]Ptr  afAbsAxisDot[0][2] + afDimA[1]Ptr  afAbsAxisDot[1][2] + afDimA[2]Ptr  afAbsAxisDot[2][2] ) + ( afDimB[2] ) ) ) > 0.0f )
            return false;

        //if not contact set, do extra tests to avoid reporting false collisions in parallell axis threshold zone
        if( !bParallellAxes  )
        {

            //Axis Ax X Bx
            if( ( fOverlap = fabsf( ( fProj = afAxisDot[1][0]Ptr  afAxesADotDiff[2] - afAxisDot[2][0]Ptr  afAxesADotDiff[1] ) ) -
                ( ( afDimA[1]Ptr  afAbsAxisDot[2][0] + afDimA[2]Ptr  afAbsAxisDot[1][0] ) + ( afDimB[1]Ptr  afAbsAxisDot[0][2] + afDimB[2]Ptr  afAbsAxisDot[0][1] ) ) ) > 0.0f )
                return false;

            //Axis Ax X By
            if( ( fOverlap = fabsf( ( fProj = afAxisDot[1][1]Ptr  afAxesADotDiff[2] - afAxisDot[2][1]Ptr  afAxesADotDiff[1] ) ) -
                ( ( afDimA[1]Ptr  afAbsAxisDot[2][1] + afDimA[2]Ptr  afAbsAxisDot[1][1] ) + ( afDimB[0]Ptr  afAbsAxisDot[0][2] + afDimB[2]Ptr  afAbsAxisDot[0][0] ) ) ) > 0.0f )
                return false;

            //Axis Ax X Bz
            if( ( fOverlap = fabsf( ( fProj = afAxisDot[1][2]Ptr  afAxesADotDiff[2] - afAxisDot[2][2]Ptr  afAxesADotDiff[1] ) ) -
                ( ( afDimA[1]Ptr  afAbsAxisDot[2][2] + afDimA[2]Ptr  afAbsAxisDot[1][2] ) + ( afDimB[0]Ptr  afAbsAxisDot[0][1] + afDimB[1]Ptr  afAbsAxisDot[0][0] ) ) ) > 0.0f )
                return false;

            //Axis Ay X Bx
            if( ( fOverlap = fabsf( ( fProj = afAxisDot[2][0]Ptr  afAxesADotDiff[0] - afAxisDot[0][0]Ptr  afAxesADotDiff[2] ) ) -
                ( ( afDimA[0]Ptr  afAbsAxisDot[2][0] + afDimA[2]Ptr  afAbsAxisDot[0][0] ) + ( afDimB[1]Ptr  afAbsAxisDot[1][2] + afDimB[2]Ptr  afAbsAxisDot[1][1] ) ) ) > 0.0f )
                return false;

            //Axis Ay X By
            if( ( fOverlap = fabsf( ( fProj = afAxisDot[2][1]Ptr  afAxesADotDiff[0] - afAxisDot[0][1]Ptr  afAxesADotDiff[2] ) ) -
                ( ( afDimA[0]Ptr  afAbsAxisDot[2][1] + afDimA[2]Ptr  afAbsAxisDot[0][1] ) + ( afDimB[0]Ptr  afAbsAxisDot[1][2] + afDimB[2]Ptr  afAbsAxisDot[1][0] ) ) ) > 0.0f )
                return false;

            //Axis Ay X Bz
            if( ( fOverlap = fabsf( ( fProj = afAxisDot[2][2]Ptr  afAxesADotDiff[0] - afAxisDot[0][2]Ptr  afAxesADotDiff[2] ) ) -
                ( ( afDimA[0]Ptr  afAbsAxisDot[2][2] + afDimA[2]Ptr  afAbsAxisDot[0][2] ) + ( afDimB[0]Ptr  afAbsAxisDot[1][1] + afDimB[1]Ptr  afAbsAxisDot[1][0] ) ) ) > 0.0f )
                return false;

            //Axis Az X Bx
            if( ( fOverlap = fabsf( ( fProj = afAxisDot[0][0]Ptr  afAxesADotDiff[1] - afAxisDot[1][0]Ptr  afAxesADotDiff[0] ) ) -
                ( ( afDimA[0]Ptr  afAbsAxisDot[1][0] + afDimA[1]Ptr  afAbsAxisDot[0][0] ) + ( afDimB[1]Ptr  afAbsAxisDot[2][2] + afDimB[2]Ptr  afAbsAxisDot[2][1] ) ) ) > 0.0f )
                return false;

            //Axis Az X By
            if( ( fOverlap = fabsf( ( fProj = afAxisDot[0][1]Ptr  afAxesADotDiff[1] - afAxisDot[1][1]Ptr  afAxesADotDiff[0] ) ) -
                ( ( afDimA[0]Ptr  afAbsAxisDot[1][1] + afDimA[1]Ptr  afAbsAxisDot[0][1] ) + ( afDimB[0]Ptr  afAbsAxisDot[2][2] + afDimB[2]Ptr  afAbsAxisDot[2][0] ) ) ) > 0.0f )
                return false;

            //Axis Az X Bz
            if( ( fOverlap = fabsf( ( fProj = afAxisDot[0][2]Ptr  afAxesADotDiff[1] - afAxisDot[1][2]Ptr  afAxesADotDiff[0] ) ) -
                ( ( afDimA[0]Ptr  afAbsAxisDot[1][2] + afDimA[1]Ptr  afAbsAxisDot[0][2] ) + ( afDimB[0]Ptr  afAbsAxisDot[2][1] + afDimB[1]Ptr  afAbsAxisDot[2][0] ) ) ) > 0.0f )
                return false;
        } // if( bParallellAxes )

        return true;
    }
    */

    bool CollisionChecker::IsSupported_CollisionDetection()
    {
        return COL_CHECKER_IMPL::IsSupported_CollisionDetection();
    }

    bool CollisionChecker::IsSupported_ContinuousCollisionDetection()
    {
        return COL_CHECKER_IMPL::IsSupported_ContinuousCollisionDetection();
    }

    bool CollisionChecker::IsSupported_DistanceCalculations()
    {
        return COL_CHECKER_IMPL::IsSupported_DistanceCalculations();
    }

    bool CollisionChecker::IsSupported_Multithreading_Threadsafe()
    {
        return COL_CHECKER_IMPL::IsSupported_Multithreading_Threadsafe();
    }

    bool CollisionChecker::IsSupported_Multithreading_MultipleColCheckers()
    {
        return COL_CHECKER_IMPL::IsSupported_Multithreading_MultipleColCheckers();
    }

    void CollisionChecker::getContacts(const MathTools::Plane& p, const CollisionModelPtr& colModel, std::vector< MathTools::ContactPoint >& storeContatcs, float maxDist /*= 1.0f*/)
    {
        THROW_VR_EXCEPTION_IF(!colModel, "NULl data");

        // first check if plane hits bounding box
        BoundingBox bbox = colModel->getBoundingBox(false);
        // enlarge bbox by maxDist
        bbox.min -= Eigen::Vector3f(maxDist, maxDist, maxDist);
        bbox.max += Eigen::Vector3f(maxDist, maxDist, maxDist);

        std::vector <Eigen::Vector3f> ptsBB = bbox.getPoints();

        for (size_t i = 0; i < ptsBB.size(); i++)
        {
            ptsBB[i] = MathTools::transformPosition(ptsBB[i], colModel->getGlobalPose());
        }

        BoundingBox bboxGlobal(ptsBB);

        if (!bboxGlobal.planeGoesThrough(p))
        {
            // plane is not going through bounding box
            return;
        }

        // bbox was hit, test all points...
        std::vector< Eigen::Vector3f > pts = colModel->getModelVeticesGlobal();

        for (auto & pt : pts)
        {
            if (MathTools::getDistancePointPlane(pt, p) <= maxDist)
            {
                MathTools::ContactPoint contact;
                contact.n = p.n;
                contact.p = pt;
                storeContatcs.push_back(contact);
            }
        }
    }

    MultiCollisionResult CollisionChecker::checkMultipleCollisions(const CollisionModelPtr& model1, const CollisionModelPtr& model2)
    {
        return collisionCheckerImplementation->checkMultipleCollisions(model1, model2);
    }

    /*
    bool CollisionChecker::checkContinuousCollision( CollisionModelPtr model1, SbMatrix &mGoalPose1, CollisionModelPtr model2, SbMatrix &mGoalPose2, float &fStoreTOC )
    {
        return collisionCheckerImplementation->checkContinuousCollision(model1,mGoalPose1,model2,mGoalPose2, fStoreTOC);
    }
    */

} // namespace VirtualRobot

